package org.luxor.commons.workflow.component.cmd;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.activiti.bpmn.model.Activity;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.MultiInstanceLoopCharacteristics;
import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.ActivitiObjectNotFoundException;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import org.activiti.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.activiti.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.activiti.engine.impl.cmd.NeedsActiveTaskCmd;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.util.ProcessDefinitionUtil;
import org.luxor.commons.workflow.constant.TransientVariableConst;
import org.luxor.commons.workflow.constant.VariableConst;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 多实例任务-加签
 *
 * @author Mr.yan  @date 2022/2/23
 */
public class AddMultiInstanceExecutionCmd extends NeedsActiveTaskCmd<Void> {
    private static final long serialVersionUID = 1L;

    private final String collectionElementIndexVariable = "loopCounter";
    private final String NUMBER_OF_INSTANCES = "nrOfInstances";
    private final String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances";

    private final String assignee;
    private final boolean beforeInstance;

    private ExecutionEntity rootExecution;
    private ExecutionEntity miExecution;
    private Activity activity;

    public AddMultiInstanceExecutionCmd(String taskId, String assignee, boolean beforeInstance) {
        super(taskId);
        this.assignee = assignee;
        this.beforeInstance = beforeInstance;
    }

    @Override
    public Void execute(CommandContext commandContext, TaskEntity task) {
        this.miExecution = task.getExecution();
        if (this.miExecution == null) {
            throw new ActivitiObjectNotFoundException("找不到流程执行实例");
        }
        this.rootExecution = getMultiInstanceRootExecution(task.getExecution());
        if (this.rootExecution == null) {
            throw new ActivitiObjectNotFoundException("找不到父级流程执行实例");
        }

        // 是多实例会签？
        BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(rootExecution.getProcessDefinitionId());
        this.activity = (Activity) bpmnModel.getFlowElement(rootExecution.getCurrentFlowElement().getId());
        if (!(this.activity.getBehavior() instanceof MultiInstanceActivityBehavior)) {
            throw new ActivitiObjectNotFoundException("无法执行，任务不属于多实例活动节点");
        }
        MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = this.activity.getLoopCharacteristics();
        if (multiInstanceLoopCharacteristics == null) {
            throw new ActivitiObjectNotFoundException("无法执行，没有定义循环特征(multiInstanceLoopCharacteristics)");
        }

        if (this.activity.getBehavior() instanceof ParallelMultiInstanceBehavior) {
            // 添加并行会签
            addParallelMultiInstanceExecution(commandContext, task);
        } else if (this.activity.getBehavior() instanceof SequentialMultiInstanceBehavior) {
            if (this.beforeInstance) {
                // 添加前置顺序会签
                addBeforeSequentialMultiInstanceExecution(commandContext, task);
            } else {
                // 添加后置顺序会签
                addAfterSequentialMultiInstanceExecution(commandContext, task);
            }
        }
        return null;
    }

    /**
     * 添加并行签
     *
     * @param commandContext 指令上下文
     * @param task 任务实体
     */
    private void addParallelMultiInstanceExecution(CommandContext commandContext, TaskEntity task) {
        // 创建新的执行实例
        ExecutionEntity concurrentExecution = commandContext.getExecutionEntityManager().createChildExecution(rootExecution);
        concurrentExecution.setActive(true);
        concurrentExecution.setScope(false);
        concurrentExecution.setCurrentFlowElement(rootExecution.getCurrentFlowElement());

        // 获取当前节点的任务实例数
        Integer nrOfInstances = getLoopVariable(rootExecution, NUMBER_OF_INSTANCES);
        Integer nrOfActiveInstances = getLoopVariable(rootExecution, NUMBER_OF_ACTIVE_INSTANCES);
        setLoopVariable(rootExecution, NUMBER_OF_INSTANCES, nrOfInstances + 1);
        setLoopVariable(rootExecution, NUMBER_OF_ACTIVE_INSTANCES, nrOfActiveInstances + 1);
        setLoopVariable(concurrentExecution, collectionElementIndexVariable, nrOfInstances);

        // 设置受理人和候选人
        ArrayNode arrayNodes = (ArrayNode) rootExecution.getVariable(VariableConst.ASSIGNEE_LIST);
        List<String> assigneeList = new ArrayList<>();
        for (JsonNode node : arrayNodes) {
            assigneeList.add(node.asText());
        }
        if (CollectionUtils.contains(assigneeList.listIterator(), assignee)) {
            throw new ActivitiIllegalArgumentException("会签办理人中,已经存在[" + assignee + "]用户");
        } else {
            assigneeList.add(assignee);
        }
        concurrentExecution.setVariable(VariableConst.ASSIGNEE_LIST, assigneeList);
        setTransientVariable(concurrentExecution, TransientVariableConst.ASSIGNEE, assignee);

        if (concurrentExecution.isActive() && !concurrentExecution.isEnded() && !concurrentExecution.getParent().isEnded()) {
            commandContext.getHistoryManager().recordActivityStart(concurrentExecution);
            ((MultiInstanceActivityBehavior) this.activity.getBehavior()).getInnerActivityBehavior().execute(concurrentExecution);
        }
    }


    /**
     * 添加前置顺序会签
     *
     * @param commandContext 指令上下文
     * @param task 任务实体
     */
    private void addBeforeSequentialMultiInstanceExecution(CommandContext commandContext, TaskEntity task) {
        // 设置受理人和候选人
        ArrayNode arrayNodes = (ArrayNode) rootExecution.getVariable(VariableConst.ASSIGNEE_LIST);
        List<String> assigneeList = new ArrayList<>();
        int loopCounter = 0;
        for (int i = 0; i < arrayNodes.size(); i++) {
            String assigneeText = arrayNodes.get(i).asText();
            if (assigneeText.equals(task.getAssignee())) {
                if (CollectionUtils.contains(assigneeList.listIterator(), assignee)) {
                    throw new ActivitiIllegalArgumentException("会签办理人中,已经存在[" + assignee + "]用户");
                } else {
                    assigneeList.add(assignee);
                    loopCounter = i;
                }
            }
            assigneeList.add(assigneeText);
        }

        // 设置多实例参数
        Integer nrOfInstances = getLoopVariable(rootExecution, NUMBER_OF_INSTANCES);
        setLoopVariable(rootExecution, NUMBER_OF_INSTANCES, nrOfInstances + 1);
        setVariable(rootExecution.getParent(), VariableConst.ASSIGNEE_LIST, assigneeList);
        setLoopVariable(rootExecution, NUMBER_OF_ACTIVE_INSTANCES, 1);

        // 删除当前的会签实例
        commandContext.getExecutionEntityManager().deleteExecutionAndRelatedData(miExecution, "delete current multi instance", false);

        // 创建前置会签实例
        ExecutionEntity childExecution = commandContext.getExecutionEntityManager()
                .createChildExecution(rootExecution);
        childExecution.setCurrentFlowElement(rootExecution.getCurrentFlowElement());
        rootExecution.setMultiInstanceRoot(true);
        rootExecution.setActive(false);

        setVariable(childExecution, VariableConst.ASSIGNEE_LIST, assigneeList);
        setLoopVariable(childExecution, TransientVariableConst.ASSIGNEE, assigneeList.get(loopCounter));
        setLoopVariable(childExecution, collectionElementIndexVariable, loopCounter);

        commandContext.getAgenda().planContinueProcessInCompensation(childExecution);

    }

    /**
     * 添加后置顺序会签
     *
     * @param commandContext 指令上下文
     * @param task 任务实体
     */
    private void addAfterSequentialMultiInstanceExecution(CommandContext commandContext, TaskEntity task) {
        // 设置受理人和候选人
        ArrayNode arrayNodes = (ArrayNode) rootExecution.getVariable(VariableConst.ASSIGNEE_LIST);
        List<String> assigneeList = new ArrayList<>();
        for (int i = 0; i < arrayNodes.size(); i++) {
            String assigneeText = arrayNodes.get(i).asText();
            assigneeList.add(assigneeText);
            if (assigneeText.equals(task.getAssignee())) {
                if (CollectionUtils.contains(assigneeList.listIterator(), assignee)) {
                    throw new ActivitiIllegalArgumentException("会签中办理人中,已经存在[" + assignee + "]用户");
                } else {
                    assigneeList.add(assignee);
                }
            }
        }
        // 设置多实例参数
        Integer nrOfInstances = getLoopVariable(rootExecution, NUMBER_OF_INSTANCES);
        setLoopVariable(rootExecution, NUMBER_OF_INSTANCES, nrOfInstances + 1);
        setVariable(rootExecution.getParent(), VariableConst.ASSIGNEE_LIST, assigneeList);
    }

    private ExecutionEntity getMultiInstanceRootExecution(ExecutionEntity executionEntity) {
        ExecutionEntity multiInstanceRootExecution = null;
        ExecutionEntity currentExecution = executionEntity;
        while (currentExecution != null && multiInstanceRootExecution == null && currentExecution.getParent() != null) {
            if (currentExecution.isMultiInstanceRoot()) {
                multiInstanceRootExecution = currentExecution;
            } else {
                currentExecution = currentExecution.getParent();
            }
        }
        return multiInstanceRootExecution;
    }

    private Integer getLoopVariable(DelegateExecution execution, String variableName) {
        Object value = execution.getVariableLocal(variableName);
        return value == null ? 0 : (Integer) value;
    }

    private void setLoopVariable(DelegateExecution execution, String variableName, Object value) {
        execution.setVariableLocal(variableName, value);
    }

    private void setTransientVariable(DelegateExecution execution, String variableName, Object value) {
        execution.setTransientVariable(variableName, value);
    }

    private void setVariable(DelegateExecution execution, String variableName, Object value) {
        execution.setVariable(variableName, value);
    }
}
